﻿using System;
using System.Collections.Generic;
#if NETFX_CORE
using System.Collections.ObjectModel;
using System.Composition;
using System.Composition.Hosting;
using System.Composition.Convention;
#else
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.ReflectionModel;
#endif
using System.Diagnostics.Contracts;
using System.Linq;
#if NETFX_CORE
using System.Reflection;
#else
using System.Linq.Expressions;
#endif

namespace DaveSexton.Labs
{
#if NETFX_CORE
	public sealed class LabFactory : LabFactoryBase<ContainerConfiguration>
#elif !SILVERLIGHT
	internal sealed class LabFactory : LabFactoryBase<ComposablePartCatalog>
#else
	public sealed class LabFactory : LabFactoryBase<ComposablePartCatalog>
#endif
	{
		#region Public Properties
		#endregion

		#region Private / Protected
#if NETFX_CORE
		[ImportMany]
#else
		[ImportMany(typeof(ILab))]
#endif
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Assigned by MEF.")]
#if !SILVERLIGHT && !NETFX_CORE
		private IEnumerable<ILab> ImportedLabs
#else
		public IEnumerable<ILab> ImportedLabs
#endif
		{
			get;
			set;
		}

		private ICollection<Type> CatalogLabTypes
		{
			get
			{
				Contract.Ensures(Contract.Result<ICollection<Type>>() != null);

				if (catalogLabTypes == null)
				{
					var list = Catalog.LabTypes.ToList();

#if NETFX_CORE
					catalogLabTypes = new ReadOnlyCollection<Type>(list);
#else
					catalogLabTypes = list.AsReadOnly();
#endif
				}

				return catalogLabTypes;
			}
		}

		private ICollection<Type> catalogLabTypes;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="LabFactory" /> class.
		/// </summary>
		internal LabFactory(LabCatalog catalog)
			: base(catalog)
		{
			Contract.Requires(catalog != null);
		}
		#endregion

		#region Methods
#if NETFX_CORE
		protected override IEnumerable<ILab> ImportLabs(ContainerConfiguration discoveryCatalog, IList<Type> excludeLabs)
#else
		protected override IEnumerable<ILab> ImportLabs(ComposablePartCatalog discoveryCatalog, IList<Type> excludeLabs)
#endif
		{
#if NETFX_CORE
			var conventions = new ConventionBuilder();

			conventions
				.ForTypesMatching(type => typeof(ILab).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())
															 && type.GetTypeInfo().GetCustomAttribute(typeof(PartNotDiscoverableAttribute), inherit: false) == null
															 && ShouldImportLab(type, excludeLabs))
				.Export<ILab>();

			using (var container = discoveryCatalog.WithDefaultConventions(conventions).CreateContainer())
			{
				container.SatisfyImports(this);
			}
#else
			using (var filtered = new FilteredCatalog(discoveryCatalog, part => ShouldImportLab(part, excludeLabs)))
			using (var container = new CompositionContainer(filtered))
			{
				container.ComposeParts(this);
			}
#endif

			return ImportedLabs;
		}

		private bool ShouldImportLab(Type partType, IList<Type> excludeLabs)
		{
			if (excludeLabs.Contains(partType))
			{
				return false;
			}
			else if (Catalog.Activation.HasFlag(LabActivationStrategies.DiscoverExplicitLabTypesOnly))
			{
				return CatalogLabTypes.Contains(partType);
			}
			else if (Catalog.Activation.HasFlag(LabActivationStrategies.ExcludeCatalogLabTypesFromDiscovery))
			{
				return !CatalogLabTypes.Contains(partType);
			}

			return true;
		}

#if !NETFX_CORE
		private bool ShouldImportLab(ComposablePartDefinition part, IList<Type> excludeLabs)
		{
			Contract.Requires(part != null);
			Contract.Requires(excludeLabs != null);

			var partTypeLoader = ReflectionModelServices.GetPartType(part);

			return partTypeLoader == null || ShouldImportLab(partTypeLoader.Value, excludeLabs);
		}
#endif
		#endregion

#if !NETFX_CORE
		#region Nested
		private sealed class FilteredCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
		{
			public override IQueryable<ComposablePartDefinition> Parts
			{
				get
				{
					return parts;
				}
			}
			private readonly ComposablePartCatalog partCatalog;
			private readonly INotifyComposablePartCatalogChanged changeNotifier;
			private readonly IQueryable<ComposablePartDefinition> parts;

			public FilteredCatalog(ComposablePartCatalog partCatalog, Expression<Func<ComposablePartDefinition, bool>> partFilter)
			{
				Contract.Requires(partCatalog != null);
				Contract.Requires(partFilter != null);

				Contract.Assume(partCatalog.Parts != null);

				this.partCatalog = partCatalog;
				this.changeNotifier = partCatalog as INotifyComposablePartCatalogChanged;
				this.parts = partCatalog.Parts.Where(partFilter);
			}

			[ContractInvariantMethod]
			[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
			private void ObjectInvariant()
			{
				Contract.Invariant(partCatalog != null);
				Contract.Invariant(parts != null);
			}

			protected override void Dispose(bool disposing)
			{
				base.Dispose(disposing);

				if (disposing)
				{
					partCatalog.Dispose();
				}
			}

			public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed
			{
				add
				{
					if (changeNotifier != null)
					{
						changeNotifier.Changed += value;
					}
				}
				remove
				{
					if (changeNotifier != null)
					{
						changeNotifier.Changed -= value;
					}
				}
			}

			public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing
			{
				add
				{
					if (changeNotifier != null)
					{
						changeNotifier.Changing += value;
					}
				}
				remove
				{
					if (changeNotifier != null)
					{
						changeNotifier.Changing -= value;
					}
				}
			}
		}
		#endregion
#endif
	}
}